use core::{ffi::c_void, ptr::null_mut, usize};

use crate::{
    c::{
        osPriority_t, osRtxThread_t, osThreadAttr_t, osThreadExit, osThreadId_t, osThreadJoin,
        osThreadNew,
    },
    error::{parse_empty, Error},
    utils::Align8Store,
};

const CB_SIZE: usize = size_of::<osRtxThread_t>();

pub struct Thread<const STACK_SIZE: usize, const PRIORITY: osPriority_t, const ATTR: u32> {
    cb_mem: Align8Store<CB_SIZE>,
    stack_mem: Align8Store<STACK_SIZE>,
    id: osThreadId_t,
}

pub trait Task {
    fn run(&self);
}

extern "C" fn task_main<T: Task>(arg: *mut c_void) {
    let t = unsafe { &*(arg as *const T) };
    t.run();
    unsafe {
        osThreadExit();
    }
}

impl<const STACK_SIZE: usize, const PRIORITY: osPriority_t, const ATTR: u32>
    Thread<STACK_SIZE, PRIORITY, ATTR>
{
    pub const fn default() -> Self {
        Self {
            cb_mem: Align8Store::new(),
            stack_mem: Align8Store::new(),
            id: null_mut(),
        }
    }

    pub fn new_task<T: Task>(&mut self, name: &str, task: &'static T) {
        let arg = task as *const T as *mut c_void;
        self.new(name, task_main::<T>, arg);
    }

    pub fn new(&mut self, name: &str, func: extern "C" fn(_: *mut c_void), arg: *mut c_void) {
        let attr = osThreadAttr_t {
            name: name.as_ptr() as *const i8,
            attr_bits: ATTR,
            cb_mem: self.cb_mem.raw_ptr(),
            cb_size: CB_SIZE as u32,
            stack_mem: self.stack_mem.raw_ptr(),
            stack_size: STACK_SIZE as u32,
            priority: PRIORITY,
            tz_module: 0,
            reserved: 0,
        };
        self.id = unsafe { osThreadNew(Some(func), arg, &attr) };
        assert!(self.id != null_mut(), "thread new fail");
    }

    pub fn join(&self) -> Result<(), Error> {
        let r = unsafe { osThreadJoin(self.id) };
        parse_empty(r)
    }
}
